---
id: consent
title: Implementing the Consent Endpoint & UI
sidebar_label: Consent Endpoint
---

import useBaseUrl from '@docusaurus/useBaseUrl'
import Mermaid from '@theme/Mermaid'
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'

:::note

Please read the [Consent Flow Documentation](../concepts/consent.mdx) first!

:::

In this document, you will learn how to implement the Consent Endpoint using our
ORY Hydra SDKs. The goal for this document is to have document this for multiple
programming languages. If you are an expert in one of these languages, your help
is highly appreciated in improving these docs!

## Implementing the Consent HTML Form

:::note

The Consent HTML Form cannot be only a Single Page App (Client-side browser
application) or a Mobile App! There has to be a server-side component with access
to ORY Hydra's Admin Endpoint!

:::

<Tabs
  defaultValue="ui"
  values={[
    {label: 'UI', value: 'ui'},
    {label: 'NodeJS', value: 'node'},
    {label: 'HTML Example', value: 'html'},
  ]}>
<TabItem value="ui">
<img src={useBaseUrl('/img/docs/consent-endpoint.png')}/>
</TabItem>
<TabItem value="node">

:::note

Check out our
[reference implementation](https://github.com/ory/hydra-login-consent-node) of
this endpoint!

:::

```typescript title="routes/consent.ts"
// This example uses ExpressJS
import express from 'express'
import url from 'url'
import csrf from 'csurf'
import { AdminApi } from '@oryd/hydra-client'

const hydraAdmin = new AdminApi(process.env.HYDRA_ADMIN_URL)

// Sets up csrf protection. Always do this when handling HTML forms!
const csrfProtection = csrf({ cookie: true })
const router = express.Router()

router.get('/get', csrfProtection, (req, res, next) => {
  // Parses the URL query
  const query = url.parse(req.url, true).query

  // The challenge is used to fetch information about the consent request from ORY hydraAdmin.
  const challenge = String(query.consent_challenge)

  hydraAdmin
    .getConsentRequest(challenge)
    // This will be called if the HTTP request was successful
    .then(({ body }) => {
      // If a user has granted this application the requested scope, hydra will tell us to not show the UI.
      if (body.skip) {
        // You can apply logic here, for example grant another scope, or do whatever...
        // ...

        // Now it's time to grant the consent request. You could also deny the request if something went terribly wrong
        return hydraAdmin
          .acceptConsentRequest(challenge, {
            // We can grant all scopes that have been requested - hydra already checked for us that no additional scopes
            // are requested accidentally.
            grantScope: body.requestedScope,

            // ORY Hydra checks if requested audiences are allowed by the client, so we can simply echo this.
            grantAccessTokenAudience: body.requestedAccessTokenAudience,

            // The session allows us to set session data for id and access tokens
            session: {
              // This data will be available when introspecting the token. Try to avoid sensitive information here,
              // unless you limit who can introspect tokens.
              // accessToken: { foo: 'bar' },
              // This data will be available in the ID token.
              // idToken: { baz: 'bar' },
            }
          })
          .then(({ body }) => {
            // All we need to do now is to redirect the user back to hydra!
            res.redirect(String(body.redirectTo))
          })
      }

      // If consent can't be skipped we MUST show the consent UI.
      res.render('consent', {
        csrfToken: req.csrfToken(),
        challenge: challenge,
        // We have a bunch of data available from the response, check out the API docs to find what these values mean
        // and what additional data you have available.
        requested_scope: body.requestedScope,
        user: body.subject,
        client: body.client
      })
    })
    // This will handle any error that happens when making HTTP calls to hydra
    .catch(next)
})
```

</TabItem>
<TabItem value="html">

```html
<form action="/consent" method="POST">
  <input type="hidden" name="challenge" value="#{ challenge }" />
  <input type="hidden" name="_csrf" value="#{ csrfToken }" />
  <p>
    Hi #{ user }, application <strong>#{ client.client_name }</strong> wants
    access resources on your behalf and to:
  </p>

  <!--
    In a real application, you would iterate over all the scopes the OAuth2 Client has requested here.

    For the sake of simplicity, we'll just show the two scopes `openid` and `offline` here:
    -->
  <input type="checkbox" id="openid" value="openid" name="grant_scope" />
  <label for="openid">openid</label><br />

  <input type="checkbox" id="offline" value="offline" name="grant_scope" />
  <label for="offline">offline</label><br />

  <p>
    Do you want to be asked next time when this application wants to access your
    data? The application will not be able to ask for more permissions without
    your consent.
  </p>

  <input type="checkbox" id="remember" name="remember" value="1" />
  <label for="remember">Do not ask me again</label><br />

  <input type="submit" id="accept" name="submit" value="Allow access" />
  <input type="submit" id="reject" name="submit" value="Deny access" />
</form>
```

</TabItem>
</Tabs>

## Accepting the Consent Request

<Tabs
  defaultValue="node"
  values={[
    {label: 'NodeJS', value: 'node'},
  ]}>
<TabItem value="node">

:::note

Check out our
[reference implementation](https://github.com/ory/hydra-login-consent-node) of
this endpoint!

:::

```typescript
hydraAdmin
  .acceptConsentRequest(challenge, {
    // We can grant all scopes that have been requested - hydra already checked for us that no additional scopes
    // are requested accidentally.
    grantScope: ['email', 'openid'],

    // The session allows us to set session data for id and access tokens
    session: {
      // This data will be available when introspecting the token. Try to avoid sensitive information here,
      // unless you limit who can introspect tokens.
      access_token: {
        // foo: 'bar'
      },

      // This data will be available in the ID token.
      id_token: {
        // baz: 'bar'
      }
    },

    // This tells hydra to remember this consent request and allow the same client to request the same
    // scopes from the same user, without showing the UI, in the future.
    remember: true,

    // When this "remember" sesion expires, in seconds. Set this to 0 so it will never expire.
    rememberFor: 3600
  })
  .then(({ body }) => {
    // All we need to do now is to redirect the user back to hydra!
    res.redirect(String(body.redirectTo))
  })
// This will handle any error that happens when making HTTP calls to hydra
// .catch(next);
```

</TabItem>
</Tabs>

## Rejecting the Consent Request

<Tabs
  defaultValue="node"
  values={[
    {label: 'NodeJS', value: 'node'},
  ]}>
<TabItem value="node">

:::note

Check out our
[reference implementation](https://github.com/ory/hydra-login-consent-node) of
this endpoint!

:::

```typescript
hydraAdmin
  .rejectConsentRequest(challenge, {
    error: 'access_denied',
    errorDescription: 'The resource owner denied the request'
  })
  .then(({ body }) => {
    // All we need to do now is to redirect the browser back to hydra!
    res.redirect(String(body.redirectTo))
  })
// This will handle any error that happens when making HTTP calls to hydra
// .catch(next);
```

</TabItem>
</Tabs>

## Complete Endpoint

<Tabs
  defaultValue="node"
  values={[
    {label: 'NodeJS', value: 'node'},
  ]}>
<TabItem value="node">

:::note

Check out our
[reference implementation](https://github.com/ory/hydra-login-consent-node) of
this endpoint!

:::

```typescript title="routes/consent.ts"
// This is the endpoint the user ends up at once she/he inserts their password and username and hits "Log in".
router.post('/consent', csrfProtection, (req, res, next) => {
  // The challenge is now a hidden input field, so let's take it from the request body instead
  const challenge = req.body.challenge;

  // Let's see if the user decided to accept or reject the consent request..
  if (req.body.submit !== 'Allow access') {
    // Looks like the consent request was denied by the user
    return hydraAdmin.rejectConsentRequest(challenge, {
      error: 'access_denied',
      errorDescription: 'The resource owner denied the request'
    }).then(({body}) => {
        // All we need to do now is to redirect the browser back to hydra!
        res.redirect(String(body.redirectTo));
      })
      // This will handle any error that happens when making HTTP calls to hydra
      .catch(next);
  }

  // Helps with form arrays
  let grantScope = req.body.grant_scope
  if (!Array.isArray(grantScope)) {
    grantScope = [grantScope]
  }

  // The session allows us to set session data for id and access tokens
  let session = {
    // This data will be available when introspecting the token. Try to avoid sensitive information here,
    // unless you limit who can introspect tokens.
    accessToken: {
      foo: 'bar'
    },

    // This data will be available in the ID token.
    idToken: {
      // baz: 'bar'
    },
  }

  // Here is also the place to add data to the ID or access token. For example,
  // if the scope 'profile' is added, add the family and given name to the ID Token claims:
  if (grantScope.indexOf('profile')) {
    session.idToken.family_name = 'Doe'
    session.idToken.given_name = 'John'
  }

  // Let's fetch the consent request again to be able to set `grantAccessTokenAudience` properly.
  hydraAdmin.getConsentRequest(challenge)
    // This will be called if the HTTP request was successful
    .then(({body}) => {
      return hydraAdmin.acceptConsentRequest(challenge, {
        // We can grant all scopes that have been requested - hydra already checked for us that no additional scopes
        // are requested accidentally.
        grantScope: grantScope,

        // The session allows us to set session data for id and access tokens
        session: session,

        // ORY Hydra checks if requested audiences are allowed by the client, so we can simply echo this.
        grantAccessTokenAudience: body.`requestedAccessTokenAudience`,

        // This tells hydra to remember this consent request and allow the same client to request the same
        // scopes from the same user, without showing the UI, in the future.
        remember: Boolean(req.body.remember),

        // When this "remember" sesion expires, in seconds. Set this to 0 so it will never expire.
        rememberFor: 3600,
      }).then(({body}) => {
        // All we need to do now is to redirect the user back to hydra!
        res.redirect(String(body.redirectTo));
      })
    })
    // This will handle any error that happens when making HTTP calls to hydra
    .catch(next);
});
```

</TabItem>
</Tabs>
